﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;
using snat.algorithms.network.TFIDF;
using System.Threading.Tasks;

namespace snat.algorithms.node.LinkPrediction {
    /// <summary>
    /// Link Predictor Node used to train interaction prediction algorithms.
    /// Contains all relevant information for training this particular node.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class LinkPredictorNode<T> : ClusterNode where T : Document {

        // TFIDF requirements
        public static Dictionary<String, Term> Terms = new Dictionary<String, Term>();
        public int DocumentCount {
            get {
                return SentDocuments.Count;
            }
        }
        public List<Document> SentDocuments = new List<Document>();
        // TFIDF cosine similarity threshold
        public double threshold = 0.35;

        // store training information for Message Rate Link Prediction
        public Dictionary<int, Dictionary<LinkPredictorNode<T>, int>> rateRecipientCount;
        public Dictionary<int, int> rateMsgSent;

        // store training information for Message Content Link Prediction
        public Dictionary<Document, Dictionary<LinkPredictorNode<T>, int>> contentRecipientCount;
        public Dictionary<Document, int> contentMsgSent;

        // store training information for Long Term Message History Link Prediction
        public int totalSent;
        public Dictionary<int, int> totalSentTo;

        // store training information for Short Term Message History Link Prediction
        public Dictionary<int, Dictionary<LinkPredictorNode<T>, int>> SThistoryRecipientCount;
        public Dictionary<int, int> SThistoryMsgSent;

        public LinkPredictorNode(int _id, string _name, int initialCluster = 0)
            : base(_id, _name, initialCluster) {

            rateRecipientCount = new Dictionary<int, Dictionary<LinkPredictorNode<T>, int>>();
            rateMsgSent = new Dictionary<int, int>();

            contentMsgSent = new Dictionary<Document, int>();
            contentRecipientCount = new Dictionary<Document, Dictionary<LinkPredictorNode<T>, int>>();

            SThistoryMsgSent = new Dictionary<int,int>();
            SThistoryRecipientCount = new Dictionary<int,Dictionary<LinkPredictorNode<T>,int>>();

            for (int i = 0; i < 10; i++) {
                rateRecipientCount.Add(i, new Dictionary<LinkPredictorNode<T>,int>());
                rateMsgSent.Add(i, 0);
                SThistoryRecipientCount.Add(i, new Dictionary<LinkPredictorNode<T>,int>());
                SThistoryMsgSent.Add(i, 0);
            }
            
            totalSentTo = new Dictionary<int, int>();
            totalSent = 0;
        }


        // maintain the correct list of documents from the last "week"
        private static DateTime start = NetworkModel.Instance.CurrentTime;
        private static TimeSpan oneWeek = new TimeSpan(7, 0, 0, 0);
        public void OnMessageProcessed(Message m) {
            SentDocuments.RemoveAll(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek);
        }

        public double predictWithHistoryST(LinkPredictorNode<T> lpn) {
            // return calculateSThistoryRate(lpn);

            double result = calculateSThistoryRate(lpn);
            if (result != 0) { return result; }
            double result2 = 0;
            if (!SThistoryRecipientCount[0].ContainsKey(lpn)) {
                result2 = 0;
            } else { result2 = (double)SThistoryRecipientCount[0][lpn] / (double)SThistoryMsgSent[0]; }
            return result2;
        }

        public double predictWithHistorySTclassifier(LinkPredictorNode<T> lpn) {
            int i = calculateSThistoryRateIndex(lpn);

            if (!SThistoryRecipientCount[i].ContainsKey(lpn)) {
                return 0;
            }
            return (double)SThistoryRecipientCount[i][lpn] / (double)SThistoryMsgSent[i];
        }

        public double predictWithClusterMR(LinkPredictorNode<T> lpn) {
            int i = calculateMRindex();
            if (!rateRecipientCount[i].ContainsKey(lpn)) {
                //return 0;
                return predictWithHistoryLT(lpn);
            }
            return (double)rateRecipientCount[i][lpn] / (double)rateMsgSent[i];
        }

        public double predictWithClusterContent(LinkPredictorNode<T> lpn) {
            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(SentDocuments,
                d => { d.PopulateTFIDF(termsDict); });

            Document dcluster = calculateClusterDoc();
            dcluster.PopulateTFIDF(termsDict);
            double mostsimilar = 0;
            Document mostsimilarDoc = null;
            foreach (Document d in contentMsgSent.Keys) {
                double cs = d.CosineSimilarity(dcluster);
                if (cs > threshold) {
                    if (cs > mostsimilar) {
                        mostsimilar = cs;
                        mostsimilarDoc = d;
                    }
                }
            }

            if (mostsimilarDoc == null || !contentRecipientCount[dcluster].ContainsKey(lpn)) {
                return predictWithHistoryLT(lpn);
            }

            return (double)contentRecipientCount[dcluster][lpn] / (double)contentMsgSent[dcluster];
        }

        public double predictWithHistoryLT(LinkPredictorNode<T> lpn) {
            if (!totalSentTo.ContainsKey(lpn.ID) || totalSent == 0) {
                return 0;
            }
            return (double)totalSentTo[lpn.ID] / (double)totalSent;
        }

        public int calculateMRindex() {
            int clusterCount = 0;
            int totalCount = 0;
            foreach (LinkPredictorNode<T> n in NetworkModel.Instance.Nodes.Values) {
                if (n.ClusterID == ClusterID) {
                    foreach (Document d in n.SentDocuments) {
                        foreach (LinkPredictorNode<T> n2 in d.Recipients) {
                            totalCount++;
                            if (n2.ClusterID == ClusterID) {
                                clusterCount++;
                            }
                        }
                    }
                }
            }
            double rate;
            if (totalCount == 0) {
                rate = 0;
            } else {
                rate = (double)clusterCount / (double)totalCount;
            }
            int i = 0;
            for (int j = 0; j < 9; j++) {
                if (rate < (0.09 + (0.1 * (double)i))) {
                    break;
                }
                i++;
            }
            return i;
        }

        public double calculateSThistoryRate(LinkPredictorNode<T> lpn) {
            int count = 0;
            int totalCount = 0;
            foreach (Document d in SentDocuments) {
                foreach (LinkPredictorNode<T> n in d.Recipients) {
                    totalCount++;
                    if (n.ID == lpn.ID) {
                        count++;
                    }
                }
            }
            double rate;
            if (totalCount == 0) {
                rate = 0;
            } else {
                rate = (double)count / (double)totalCount;
            }
            return rate;
        }

        public int calculateSThistoryRateIndex(LinkPredictorNode<T> lpn) {
            double rate = calculateSThistoryRate(lpn);
            int i = 0;
            for (int j = 0; j < 9; j++) {
                if (rate < (0.09 + (0.1 * (double)i))) {
                    break;
                }
                i++;
            }
            return i;
        }

        public Document calculateClusterDoc() {
            List<Document> clusterDocs = new List<Document>();
            foreach (LinkPredictorNode<T> n in NetworkModel.Instance.Nodes.Values) {
                if (n.ClusterID == ClusterID) {
                    foreach (Document d in n.SentDocuments) {
                        foreach (LinkPredictorNode<T> n2 in d.Recipients) {
                            if (n2.ClusterID == ClusterID) {
                                clusterDocs.Add(d);
                            }
                        }
                    }
                }
            }

            // Create time windowed TFIDF document of all messages within cluster.
            return new Document(clusterDocs);
        }

        public void OnMessageSend(Message m, BaseNode bn) {
            // train message cluster rate classifier
            int i = calculateMRindex();
            foreach (LinkPredictorNode<T> lpn in m.Recipients) {
                if (!rateRecipientCount[i].ContainsKey(lpn)) {
                    rateRecipientCount[i].Add(lpn, 0);
                }
                rateMsgSent[i]++;
                rateRecipientCount[i][lpn]++;
            }

            // train Short Term History classifier
            foreach (LinkPredictorNode<T> lpn in m.Recipients) {
                int j = calculateSThistoryRateIndex(lpn);
                if (!SThistoryRecipientCount[j].ContainsKey(lpn)) {
                    SThistoryRecipientCount[j].Add(lpn, 0);
                }
                SThistoryMsgSent[j]++;
                SThistoryRecipientCount[j][lpn]++;
            }

            // get all the documents from within the cluster (excluding the one just sent)
            Document clusterdoc = calculateClusterDoc();

            // create a document from the message and add to sent docs.
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, true });
            SentDocuments.Add(document);

            Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
            Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
            Parallel.ForEach<Document>(SentDocuments,
                d => { d.PopulateTFIDF(termsDict); });

            // populate TFIDF for the "clusterdoc"
            clusterdoc.PopulateTFIDF(termsDict);
            // find most similar currently recorded message (above a threshold)
            double mostsimilar = 0;
            Document mostsimilarDoc = null;
            foreach (Document d in contentMsgSent.Keys) {
                double cs = d.CosineSimilarity(clusterdoc);
                if (cs > threshold) {
                    if (cs > mostsimilar) {
                        mostsimilar = cs;
                        mostsimilarDoc = d;
                    }
                }
            }
            // if no document is similar enough, add new document as its own key
            if (mostsimilarDoc == null) {
                contentMsgSent.Add(clusterdoc, 0);
                contentRecipientCount.Add(clusterdoc, new Dictionary<LinkPredictorNode<T>, int>());
            }
            foreach (LinkPredictorNode<T> lpn in m.Recipients) {
                contentMsgSent[clusterdoc]++;
                if (!contentRecipientCount[clusterdoc].ContainsKey(lpn)) {
                    contentRecipientCount[clusterdoc].Add(lpn, 0);
                }
                contentRecipientCount[clusterdoc][lpn]++;
            }

            // train a classifier for all history between two nodes.
            foreach (LinkPredictorNode<T> lpn in m.Recipients) {
                if (!totalSentTo.ContainsKey(lpn.ID)) {
                    totalSentTo.Add(lpn.ID, 0);
                }
                totalSentTo[lpn.ID]++;
                totalSent++;
            }
        }
    }
}
